package com.dycong.common.web.wrapper;

import com.ifunpay.util.function.TryToDo;
import lombok.Getter;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Created by dycong on 2015/4/2.
 * todo 可重置的read httpServletRequest 可用于需要读取请求数据并仍需要保留request数据完整的情景，例如在 Filter、拦截器等场景
 * todo 此处通过 装饰者模式将 request 里的数据预先读取并保存到 byte[] rawData 在之后的调用读取rawData的数据，在需要将 request
 * todo 传递到下一个 Filter 时，set方法
 */
public class ResettableStreamHttpServletRequest extends
        HttpServletRequestWrapper {

    @Getter
    private byte[] rawData;
    private HttpServletRequest request;
    private ResettableServletInputStream servletStream;

    Map<String, String> paramMap;

    private Logger logger = Logger.getLogger(com.ifunpay.util.web.wrapper.ResettableStreamHttpServletRequest.class);

    public ResettableStreamHttpServletRequest(HttpServletRequest request) {
        super(request);
        this.request = request;
        this.servletStream = new ResettableServletInputStream();
    }

    //// TODO: 2017/5/10 是servletRequest从我们缓存的数据 rawData 重新获取 readStream
    public void resetInputStream() {
        servletStream.stream = new ByteArrayInputStream(rawData);
    }

    private void initRawData() throws IOException {
        if (rawData == null) {
            rawData = IOUtils.toByteArray(request.getInputStream());
            if (logger.isTraceEnabled()) {
                logger.trace("UTF8 >>> " + new String(rawData, "UTF-8"));
            }
        }
        servletStream.stream = new ByteArrayInputStream(rawData);
    }
//// TODO: 2017/5/10 覆盖了上层 servlet 的 getInputStream() 使其后的 Filter 在调用getInputStream时得到的是是我们缓存的数据
    @Override
    public ServletInputStream getInputStream() throws IOException {
        initRawData();
        return servletStream;
    }

    public Map<String, String> getParamMap() {
        if (paramMap == null) {
            try {
                initRawData();
                String rex = "^(.+)=(.+)$";
                String in = new String(rawData, "UTF-8");
                paramMap = Arrays.stream(in.split("&")).collect(Collectors.toMap(
                        s -> s.replaceAll(rex, "$1"),
                        s -> TryToDo.tryToGet(() -> URLDecoder.decode(s.replaceAll(rex, "$2"), "UTF-8")).orElse("")
                ));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return paramMap;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        initRawData();
        return new BufferedReader(new InputStreamReader(servletStream));
    }


    @Override
    public Enumeration getParameterNames() {
        return Collections.enumeration(getParamMap().keySet());
    }

    @Override
    public Map getParameterMap() {
        return getParamMap();
    }

    @Override
    public String getParameter(String name) {
        return getParamMap().get(name);
    }


    @Override
    public String[] getParameterValues(String name) {
        String parameter = getParameter(name);
        if (parameter == null)
            parameter = "";
        return parameter.split(";");
    }

    private class ResettableServletInputStream extends ServletInputStream {

        private InputStream stream;

        @Override
        public int read() throws IOException {
            return stream.read();
        }

    }
}